/*
 * Copyright (c) 2022, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/rtc.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include "syslog.h"

#define ATOM_NONE_STANDARDIO
#include "drv_rtc.h"


static void get_rtc_timestamp(RtcTime *tm_new)
{
    if (tm_new == NULL) {
        SYSLOG(LOG_ERR, "Null pointer provided\n");
        return;
    }

    // 获取当前时间
    time_t rawtime;
    struct tm* timeinfo;

    time(&rawtime);
    timeinfo = localtime(&rawtime);

    if (timeinfo == NULL) {
        SYSLOG(LOG_ERR, "Failed to get localtime\n");
        return;
    }

    // 填充RtcTime结构体
    tm_new->tm_sec = timeinfo->tm_sec;
    tm_new->tm_min = timeinfo->tm_min;
    tm_new->tm_hour = timeinfo->tm_hour;
    tm_new->tm_mday = timeinfo->tm_mday;
    tm_new->tm_mon = timeinfo->tm_mon;    // tm_mon是从0开始的，需要加1
    tm_new->tm_year = timeinfo->tm_year; // tm_year是从1900开始的，需要加1900
    tm_new->tm_wday = timeinfo->tm_wday;
    tm_new->tm_yday = timeinfo->tm_yday;
    tm_new->tm_isdst = timeinfo->tm_isdst;
}

static int set_rtc_time_stamp(RtcTime* time_stamp)
{
    int rtc_fd;
    time_t raw_time;

    // Validate the year (assuming the year should be 100 or more as per the original check)
    if (time_stamp->tm_year < 100)
    {
        return NG;
    }

    // Open the RTC device
    //rtc_fd = open("/dev/rtc", O_RDONLY);
    if (rtc_fd == -1)
    {
        perror("ioctl");
        SYSLOG(LOG_ERR, "Failed to open RTC device");
        return NG;
    }

    // 将rtc_time结构体转换为time_t
    struct tm tm = {
        .tm_sec = time_stamp->tm_sec,
        .tm_min = time_stamp->tm_min,
        .tm_hour = time_stamp->tm_hour,
        .tm_mday = time_stamp->tm_mday,
        .tm_mon = time_stamp->tm_mon,
        .tm_year = time_stamp->tm_year,
        .tm_isdst = -1  // 让库函数自行计算是否为夏令时
    };

    // Convert the time to UTC time
    raw_time = mktime(&tm);
    // 转换为UTC时间
    struct tm* utc_time = gmtime(&raw_time);
    struct rtc_time rtc_tm;

    // Prepare the rtc_time structure
    rtc_tm.tm_sec = utc_time->tm_sec;
    rtc_tm.tm_min = utc_time->tm_min;
    rtc_tm.tm_hour = utc_time->tm_hour;
    rtc_tm.tm_mday = utc_time->tm_mday;
    rtc_tm.tm_mon = utc_time->tm_mon;
    rtc_tm.tm_year = utc_time->tm_year;
    rtc_tm.tm_wday = utc_time->tm_wday;
    rtc_tm.tm_yday = utc_time->tm_yday;
    rtc_tm.tm_isdst = 0; // UTC does not have daylight saving time

    // Set the RTC time
    if (ioctl(rtc_fd, RTC_SET_TIME, &rtc_tm) == -1)
    {
        perror("ioctl");
        SYSLOG(LOG_ERR, "Failed to set RTC time");
        close(rtc_fd);
        return NG;
    }

    // Close the RTC device
    //close(rtc_fd);

    return OK; // Success
}

static void rtc_alarm_handler(union sigval sv) {
    RtcWakeAlarm* alarm = (RtcWakeAlarm*)sv.sival_ptr;
    if (alarm->callback) {
        alarm->callback(alarm);
    }
}

static void set_rtc_alarm(RtcWakeAlarm *alarm)
{
    if (alarm->enable == 0) {
        // Disable the timer if it is enabled
        if (alarm->timer_id != 0) {
            timer_delete(alarm->timer_id);
            alarm->timer_id = 0;
        }
        return;
    }

    // Create the timer if it is not created
    if (alarm->timer_id == 0) {
        struct sigevent sev;
        sev.sigev_notify = SIGEV_THREAD;
        sev.sigev_value.sival_ptr = alarm;
        sev.sigev_notify_function = rtc_alarm_handler;
        sev.sigev_notify_attributes = NULL;

        if (timer_create(CLOCK_REALTIME, &sev, &alarm->timer_id) == -1) {
            perror("timer_create");
            return;
        }
    }

    // Set the timer
    struct itimerspec its;
    struct tm alarm_time = { 0 };
    time_t current_time = time(NULL);
    struct tm* current_tm = localtime(&current_time);

    alarm_time.tm_sec = alarm->tm_sec;
    alarm_time.tm_min = alarm->tm_min;
    alarm_time.tm_hour = alarm->tm_hour;
    alarm_time.tm_mday = current_tm->tm_mday;
    alarm_time.tm_mon = current_tm->tm_mon;
    alarm_time.tm_year = current_tm->tm_year;

    time_t alarm_epoch = mktime(&alarm_time);
    if (alarm_epoch == -1) {
        perror("mktime");
        return;
    }

    its.it_value.tv_sec = alarm_epoch - current_time;
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 0;

    if (timer_settime(alarm->timer_id, 0, &its, NULL) == -1) {
        perror("timer_settime");
    }
}

static void get_rtc_alarm(RtcWakeAlarm *alarm)
{
    if (alarm->timer_id != 0) {
        struct itimerspec its;
        if (timer_gettime(alarm->timer_id, &its) == -1) {
            perror("timer_gettime");
            return;
        }

        time_t alarm_epoch = time(NULL) + its.it_value.tv_sec;
        struct tm* alarm_tm = localtime(&alarm_epoch);

        alarm->tm_sec = alarm_tm->tm_sec;
        alarm->tm_min = alarm_tm->tm_min;
        alarm->tm_hour = alarm_tm->tm_hour;
    }
    else {
        // Timer is not set, return default values
        alarm->tm_sec = 0;
        alarm->tm_min = 0;
        alarm->tm_hour = 0;
    }
}

static void rtc_wakeup_handler(union sigval sv) {
    RtcWakeUp* wakeup = (RtcWakeUp*)sv.sival_ptr;
    if (wakeup->callback) {
        wakeup->callback(wakeup);
    }
}


static void set_rtc_wakeup(RtcWakeUp *wakeup)
{
    if (wakeup->enable == 0) {
        // Disable the timer if it is enabled
        if (wakeup->timer_id != 0) {
            timer_delete(wakeup->timer_id);
            wakeup->timer_id = 0;
        }
        return;
    }

    // Create the timer if it is not created
    if (wakeup->timer_id == 0) {
        struct sigevent sev;
        sev.sigev_notify = SIGEV_THREAD;
        sev.sigev_value.sival_ptr = wakeup;
        sev.sigev_notify_function = rtc_wakeup_handler;
        sev.sigev_notify_attributes = NULL;

        if (timer_create(CLOCK_REALTIME, &sev, &wakeup->timer_id) == -1) {
            perror("timer_create");
            return;
        }
    }

    // Set the timer
    struct itimerspec its;

    uint32_t interval_sec;
    switch (wakeup->clock) {
    case RTCCLK_Div16:
        interval_sec = wakeup->counter * 16;
        break;
    case RTCCLK_Div8:
        interval_sec = wakeup->counter * 8;
        break;
    case RTCCLK_Div4:
        interval_sec = wakeup->counter * 4;
        break;
    case RTCCLK_Div2:
        interval_sec = wakeup->counter * 2;
        break;
    default:
        fprintf(stderr, "Invalid wakeup clock source\n");
        return;
    }

    its.it_value.tv_sec = interval_sec;
    its.it_value.tv_nsec = 0;
    its.it_interval.tv_sec = 0;
    its.it_interval.tv_nsec = 0;

    if (timer_settime(wakeup->timer_id, 0, &its, NULL) == -1) {
        perror("timer_settime");
        return;
    }
}

static int rtc_ioctl(FD fd, int function, void* arg)
{
    switch (function)
    {
    case RTC_CTRL_GET_DATETIME:
        get_rtc_timestamp((RtcTime*)arg);
        break;
    case RTC_CTRL_SET_DATETIME:
        if (set_rtc_time_stamp((RtcTime *)arg))
        {
            return NG;
        }
        break;
    case RTC_CTRL_GET_ALARM:
        set_rtc_alarm((RtcWakeAlarm *)arg);
        break;
    case RTC_CTRL_SET_ALARM:
        get_rtc_alarm((RtcWakeAlarm *)arg);
        break;
    case RTC_CTRL_SET_WAKEUP:
        set_rtc_wakeup((RtcWakeUp *)arg);
        break;
    }

    return OK;
}

static void rtc_init(void)
{

}



void UpRtcCreate(UpboardRtc *rtc)
{
    RtcCreate(&rtc->Super, rtc_ioctl);
    rtc_init();
}
